using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using SynapseGaming.LightingSystem.Core;
using SynapseGaming.LightingSystem.Lights;
using SynapseGaming.LightingSystem.Rendering;
using Brain.Skinning;
using Microsoft.Xna.Framework;
using SynapseGaming.LightingSystem.Effects;
using Brain.Rendering;
#if !WINDOWS_PHONE
using SynapseGaming.LightingSystem.Effects.Deferred;
using Brain.Rendering;
#endif

namespace Brain.SunBurn
{
    public class SunBurnRenderManager3D : IRenderManager3D
    {
        public GraphicsDevice GraphicsDevice { get; set; }

        /// <summary>
        /// SunBurn Core System (old LightingSystemManager)
        /// </summary>
        public SunBurnCoreSystem SunBurnCoreSystem;

        /// <summary>
        /// Frame Buffers
        /// </summary>
        public FrameBuffers FrameBuffers;

        /// <summary>
        /// Scene State
        /// </summary>
        public SceneState SceneState;
        /// <summary>
        /// Scene Interface
        /// </summary>
        public SceneInterface SceneInterface;
        /// <summary>
        /// Scene Environment
        /// </summary>
        public SceneEnvironment Environment;

        /// <summary>
        /// 'Rendering to Screen
        /// </summary>
        public bool RenderingToScreen = true;

        /// <summary>
        /// Use Deferred Rendering
        /// </summary>
        public RenderingSystemType RenderingType { get; protected set; }

        public List<Action> BeforeRenderActions { get; set; }
        public List<Action> AfterRenderActions { get; set; }

        public bool ShouldDraw { get; set; }
        public bool ShouldUpdate { get; set; }

        public ShapeDrawer LineDrawer;

        public SunBurnRenderManager3D(GraphicsDevice device, RenderingSystemType renderSystem)
        {
            LineDrawer = new ShapeDrawer();

            BeforeRenderActions = new List<Action>();
            AfterRenderActions = new List<Action>();

            ShouldDraw = true;
            ShouldUpdate = true;

            this.GraphicsDevice = device;
            this.RenderingType = renderSystem;

            SunBurnCoreSystem = new SunBurnCoreSystem(Engine.Instance.ServiceProvider, Engine.Instance.ContentManager.ContentManager);
            SceneState = new SceneState();

            SceneInterface = new SceneInterface();
            SceneInterface.CreateDefaultManagers(RenderingType, false);

            FrameBuffers = new FrameBuffers(DetailPreference.Medium, DetailPreference.Medium);
            SceneInterface.ResourceManager.AssignOwnership(FrameBuffers);

            Environment = new SceneEnvironment();
            Environment.VisibleDistance = 500;
            Environment.FogEnabled = true;
            Environment.FogStartDistance = 450;
            Environment.FogEndDistance = 500;
            Environment.ShadowFadeStartDistance = 400;
            Environment.ShadowFadeEndDistance = 450;
            Environment.ShadowCasterDistance = 450;

            SceneInterface.ShowStatistics = false;
            SceneInterface.StatisticCategories = SystemStatisticCategory.None;

            if (Engine.Instance.RunningEditor)
            {
                SunBurnCoreSystem.DetectOverSizedFrameBuffers = false;
            }
        }

        /// <summary>
        /// Renders the scene objects contained within the ObjectManager to the currently
        /// active RenderTargetHelper (should be called between calls to
        /// RenderTargetHelper.BeginFrameRendering and RenderTargetHelper.EndFrameRendering).
        /// </summary>
        /// <param name="target">The currently active RenderTargetHelper</param>
        public void RenderTarget(RenderTargetHelper target)
        {
            // Apply the currently active RenderTargetHelper's preferences
            // and scene state to the SceneInterface.  This is critical for
            // proper reflection, using the main scene's scene state will
            // not work.
            SceneInterface.ApplyPreferences(target.Preferences);
            SceneInterface.BeginFrameRendering(target.SceneState);

            // Render the scene.
            SceneInterface.RenderManager.Render();

            SceneInterface.EndFrameRendering();
        }

        public void StartDraw(GameScreen gs)
        {
        }

        public void FinishDraw(GameScreen gs)
        {
        }

        private List<IWorldObject> editor_WorldObjects;
        public void SubmitWorldObject(IWorldObject worldObject)
        {
            if (Engine.Instance.RunningEditor)
            {
                if (editor_WorldObjects == null)
                {
                    editor_WorldObjects = new List<IWorldObject>();
                }
                editor_WorldObjects.Add(worldObject);
            }

            if (worldObject.RenderingTag is SceneObject)
            {
                SceneObject scene = worldObject.RenderingTag as SceneObject;

                this.SceneInterface.ObjectManager.Submit(scene);
            }
            else
            {
                if (worldObject.Model is Model)
                {
                    Model model = worldObject.Model as Model;
                    SceneObject scene = new SceneObject(model);
                    worldObject.RenderingTag = scene;
                    this.SceneInterface.ObjectManager.Submit(scene);
                }
                else if (worldObject.Model is SkinnedModel)
                {
                    SkinnedModel model = worldObject.Model as SkinnedModel;
                    SceneObject scene = new SceneObject(model.Model);
                    worldObject.RenderingTag = scene;
                    this.SceneInterface.ObjectManager.Submit(scene);
                }
                else if (worldObject.Model is BrainModel)
                {
                    BrainModel model = worldObject.Model as BrainModel;
                    
                    foreach (BrainModelMesh mesh in model.Meshes)
                    {
                        MeshData data = new MeshData();
                        data.Effect = mesh.Effect;
                        data.IndexBuffer = mesh.IndexBuffer;
                        data.MeshToObject = Matrix.Identity;
                        data.PrimitiveCount = mesh.PrimitiveCount;
                        data.VertexBuffer = mesh.VertexBuffer;
                        data.VertexCount = mesh.NumVertices;
                        data.VertexStride = mesh.VertexBuffer.VertexDeclaration.VertexStride;

                        data.InfiniteBounds = true; // TEMPORARILY

                        SceneObject scene = new SceneObject(data);
                        this.SceneInterface.ObjectManager.Submit(scene);
                        mesh.Tag = scene;
                    }
                }
            }
        }

        public void RemoveWorldObject(IWorldObject worldObject)
        {
            if (Engine.Instance.RunningEditor)
            {
                this.editor_WorldObjects.Remove(worldObject);
            }
            if (this.SceneInterface != null)
            {
                if (worldObject.RenderingTag is SceneObject)
                {
                    this.SceneInterface.ObjectManager.Remove(worldObject.RenderingTag as SceneObject);
                }
            }
        }

        public void SubmitLight(object Light)
        {
            if (Light is BaseLight)
            {
                BaseLight light = Light as BaseLight;
                SceneInterface.LightManager.Submit(light);
            }
        }

        public void RemoveLight(object Light)
        {
            if (Light is BaseLight)
            {
                BaseLight light = Light as BaseLight;
                SceneInterface.LightManager.Remove(light);
            }
        }

        public void Reset()
        {
            SceneInterface.Unload();
            SunBurnCoreSystem.Unload();

            this.BeforeRenderActions.Clear();
            this.BeforeRenderActions.Capacity = 0;
            this.AfterRenderActions.Clear();
            this.AfterRenderActions.Capacity = 0;

            if (this.editor_WorldObjects != null)
            {
                this.editor_WorldObjects.Clear();
                this.editor_WorldObjects.Capacity = 0;
            }

            this.LineDrawer.Reset();
        }
        public void EndDraw()
        {
            if (Engine.Instance.RunningEditor)
            {
                if (!SplashScreenGameComponent.DisplayComplete)
                {
                    return;
                }
            }

            for (int i = 0; i < BeforeRenderActions.Count; i++)
            {
                BeforeRenderActions[i].Invoke();
            }

            if (Engine.Instance.CameraManager != null &&
                Engine.Instance.CameraManager.Camera3D != null)
            {
                ICamera3D Camera = Engine.Instance.CameraManager.Camera3D;
                if (Engine.Instance.StereoscopicManager == null)
                {
                    DrawEverything(Camera);
                }
                else
                {
                    if (Engine.Instance.StereoscopicManager.StereoMode == StereoMode.NoStereo)
                    {
                        DrawEverything(Camera);
                    }
                    else
                    {
                        Engine.Instance.StereoscopicManager.Draw(Camera, this);
                    }
                }
            }

            for (int i = 0; i < AfterRenderActions.Count; i++)
            {
                AfterRenderActions[i]();
            }
        }
        public void DrawEverything(ICamera3D Camera)
        {
            SceneState.BeginFrameRendering(Camera.View,
                Camera.Projection, Engine.Instance.GameTime, this.Environment,
                FrameBuffers, RenderingToScreen);
            SceneInterface.BeginFrameRendering(SceneState);

            SceneInterface.RenderManager.Render();

            SceneInterface.EndFrameRendering();
            SceneState.EndFrameRendering();

            LineDrawer.DrawEverything(Camera);
        }


        public void Draw()
        {
        }


        public void Update()
        {
            SceneInterface.Update(Engine.Instance.GameTime);
        }


        public SerializationData Data { get; set; }

        public IWorldObject Editor_GetWorldObject(int index)
        {
            return this.editor_WorldObjects[index];
        }

        public int Editor_Count()
        {
            if (this.editor_WorldObjects == null)
            {
                return 0;
            }
            return this.editor_WorldObjects.Count;
        }


        public void Submit(object obj)
        {
            if (obj is WorldObject)
            {
                this.SubmitWorldObject(obj as WorldObject);
            }
            else if (obj is SceneObject)
            {
                this.SceneInterface.ObjectManager.Submit(obj as SceneObject);
            }
            else if (obj is IPostProcessor)
            {
                this.SceneInterface.PostProcessManager.AddPostProcessor(obj as IPostProcessor);
            }
            else if (obj is BaseLight)
            {
                this.SceneInterface.LightManager.Submit(obj as BaseLight);
            }
            else
            {
                this.LineDrawer.Submit(obj);
            }
        }

        public void Remove(object obj)
        {
            if (obj is WorldObject)
            {
                this.RemoveWorldObject(obj as WorldObject);
            }
            else if (obj is SceneObject)
            {
                this.SceneInterface.ObjectManager.Remove(obj as SceneObject);
            }
            else if (obj is IPostProcessor)
            {
                this.SceneInterface.PostProcessManager.RemovePostProcessor(obj as IPostProcessor);
            }
            else if (obj is BaseLight)
            {
                this.SceneInterface.LightManager.Remove(obj as BaseLight);
            }
        }

        public void UpdateWorldObjectStatus(IWorldObject worldObject)
        {
            if (worldObject.RenderingTag is SceneObject)
            {
                SceneObject s = worldObject.RenderingTag as SceneObject;
                s.World = worldObject.WorldMatrix;
                if (worldObject.Model is SkinnedModel)
                {
                    SkinnedModel mo = worldObject.Model as SkinnedModel;
                    s.SkinBones = mo.SkinTransforms;
                }
            }
        }
         
        public void SerializeData(DefaultSerializationManager manager)
        {
        }

        public void DeserializeData(DefaultSerializationManager manager, SerializationData newData)
        {
        }



        public void InitializeTerrainDefaultRendering(TerrainWorldObject terrain, bool wipeCPUContent, bool makeTriangles)
        {
            HeightMap HeightMap = terrain.HeightMap;

            if (HeightMap != null)
            {
                int VertexCount = HeightMap.Width * HeightMap.Height;

                VertexPositionNormalTextureBump[] vertices = new VertexPositionNormalTextureBump[VertexCount];
                terrain.RenderingTag = vertices;

                float offset = HeightMap.OffsetBetweenHeights;
                for (int x = 0; x < HeightMap.Width; x++)
                {
                    for (int z = 0; z < HeightMap.Height; z++)
                    {
                        float height = HeightMap.Heights[x, z];
                        int index = x + z * HeightMap.Width;
                        vertices[index].Position = new Vector3(x * offset, height, -z * offset);
                        vertices[index].TextureCoordinate = new Vector2((float)x / 30.0f, (float)z / 30.0f);
                    }
                }

                short[] Indices = new short[(HeightMap.Width - 1) * (HeightMap.Height - 1) * 6];
                int counter = 0;
                for (int y = 0; y < HeightMap.Height - 1; y++)
                {
                    for (int x = 0; x < HeightMap.Width - 1; x++)
                    {
                        int lowerLeft = x + y * HeightMap.Width;
                        int lowerRight = (x + 1) + y * HeightMap.Width;
                        int topLeft = x + (y + 1) * HeightMap.Width;
                        int topRight = (x + 1) + (y + 1) * HeightMap.Width;

                        Indices[counter++] = (short)topLeft;
                        Indices[counter++] = (short)lowerRight;
                        Indices[counter++] = (short)lowerLeft;

                        Indices[counter++] = (short)topLeft;
                        Indices[counter++] = (short)topRight;
                        Indices[counter++] = (short)lowerRight;
                    }
                }
                
                for (short i = 0; i < Indices.Length - 2; i += 3)
                {
                    int index1 = Indices[i];
                    int index2 = Indices[i + 1];
                    int index3 = Indices[i + 2];

                    VertexPositionNormalTextureBump v1 = vertices[index1];
                    VertexPositionNormalTextureBump v2 = vertices[index2];
                    VertexPositionNormalTextureBump v3 = vertices[index3];

                    Vector3 normal = TriangleUtil.Normal(v1.Position, v2.Position, v3.Position);
                    vertices[index1].Normal = normal;
                    vertices[index2].Normal = normal;
                    vertices[index3].Normal = normal;
                }

                VertexPositionNormalTextureBump.BuildTangentSpaceDataForTriangleList(Indices, vertices);

                VertexBuffer VertexBuffer;
                if (Engine.Instance.RunningEditor)
                {
                    VertexBuffer = new DynamicVertexBuffer(Engine.Instance.GraphicsDevice, VertexPositionNormalTextureBump.VertexDeclaration,
                         vertices.Length, BufferUsage.None);
                }
                else
                {
                    VertexBuffer = new VertexBuffer(Engine.Instance.GraphicsDevice, VertexPositionNormalTextureBump.VertexDeclaration,
                         vertices.Length, BufferUsage.None);
                }
                VertexBuffer.SetData(vertices);

                IndexBuffer IndexBuffer;
                if (Engine.Instance.RunningEditor)
                {
                    IndexBuffer = new DynamicIndexBuffer(Engine.Instance.GraphicsDevice,
                        IndexElementSize.SixteenBits, Indices.Length, BufferUsage.None);
                }
                else
                {
                    IndexBuffer = new IndexBuffer(Engine.Instance.GraphicsDevice,
                        IndexElementSize.SixteenBits, Indices.Length, BufferUsage.None);
                }
                IndexBuffer.SetData(Indices);

                BrainModel model = new BrainModel();

                BrainModelMesh mesh = new BrainModelMesh();
                mesh.VertexBuffer = VertexBuffer;
                mesh.IndexBuffer = IndexBuffer;
#if WINDOWS_PHONE
                mesh.Effect = new SunBurnBasicEffect(Engine.Instance.GraphicsDevice);
#else
                mesh.Effect = new DeferredObjectEffect(Engine.Instance.GraphicsDevice);
#endif
                mesh.NumVertices = VertexBuffer.VertexCount;
                mesh.PrimitiveCount = Indices.Length / 3;
                mesh.StartIndex = 0;
                mesh.VertexOffset = 0;

                model.Meshes = new BrainModelMesh[1];
                model.Meshes[0] = mesh;

                terrain.Model = model;

                if (makeTriangles)
                {
                    terrain.Indices = Indices;
                    terrain.MakeTriangles();
                    terrain.Indices = null;
                }

                if (wipeCPUContent)
                {
                    Indices = null;
                }
                else
                {
                    terrain.Indices = Indices;
                }
            }
        }

        public Vector3 GetTerrainVertex(WorldObject terrain, int index)
        {
            VertexPositionNormalTextureBump[] vertices = terrain.RenderingTag as VertexPositionNormalTextureBump[];
            return vertices[index].Position;
        }


        public void UpdateTerrainVertexBuffer(WorldObject terrain, VertexBuffer buffer)
        {
            VertexPositionNormalTextureBump[] vertices = terrain.RenderingTag as VertexPositionNormalTextureBump[];
            buffer.SetData(vertices);
        }

        public void Editor_UpdateTerrainVertex(WorldObject terrain, int index, Vector3 newPos, Vector2 newTexCoord)
        {
            VertexPositionNormalTextureBump[] vertices = terrain.RenderingTag as VertexPositionNormalTextureBump[];
            vertices[index].Position = newPos;
            vertices[index].TextureCoordinate = newTexCoord;
        }

        public void DeserializedAll()
        {
        }
    }
}
